// Copyright 2006 Two Pair, LLC. All Rights Reserved.

/**
 * Answers whether a character is a break or non-break character. Initializes
 * and tracks the set of break and non-break characters.
 */
public class CharacterAnalyzer {

  /**
   * The first invalid character.
   */
  static final int CHARS_END = 256;
  /**
   * The default set of variable break characters.
   */
  static final String DEFAULT_BREAK_CHARS = " :/.,+*()~&%$#!`\"{}|^[]\\@?<>=;";
  /**
   * The first non-break character.
   */
  static final int NONBREAK_BEGIN = 32;
  /**
   * The first break character after all non-break characters.
   */
  static final int NONBREAK_END = 127;

  /**
   * Computes the set of break and non-break characters, based on the passed-in
   * set of variable break characters. Characters in the range 0 to 31 and 127
   * to 255 (inclusive) are always break characters. Characters in the range 32
   * to 126 are break characters if they are contained in
   * <code>breakChars</code>.
   * 
   * @param breakChars the set of unique characters, each of which must be in
   *          the range 32 to 126, which comprise the variable break characters.
   * @return an 256 element array, in which the element corresponding to each
   *         character between 0 and 255 is <code>true</code> if character is
   *         a break character and <code>false</code> otherwise.
   * @throws IllegalArgumentException if any character in
   *           <code>breakChars</code> is less than 32 or greater than 126; or
   *           if any character appears in <code>breakChars</code> more than
   *           once.
   */
  private static boolean[] computeBreakChars(String breakChars)
      throws IllegalArgumentException {
    boolean[] result = new boolean[CHARS_END];

    // Initialize characters from 0 to 31 and 127 to 255 as break characters.
    for (int i = 0; i < NONBREAK_BEGIN; ++i) {
      result[i] = true;
    }
    for (int i = NONBREAK_END; i < CHARS_END; ++i) {
      result[i] = true;
    }

    // Initialize the specified variable break characters as break characters.
    char[] breakCharsArray = breakChars.toCharArray();
    for (int i = 0; i < breakCharsArray.length; ++i) {
      char c = breakCharsArray[i];
      if (result[c]) {
        // Either a fixed character, or already saw this character.
        throw new IllegalArgumentException("'" + c
            + "' is already a break character");
      }
      result[c] = true;
    }

    return result;
  }

  /**
   * Constructs a character analyzer with the default set of break characters.
   */
  public CharacterAnalyzer() {
    isBreakChar = computeBreakChars(DEFAULT_BREAK_CHARS);
  }

  /**
   * Constructs a character analyzer with the specified set of break characters.
   * 
   * @param breakChars the set of unique characters, each of which must be in
   *          the range 32 to 126, which comprise the variable break characters.
   * @throws IllegalArgumentException if any character in
   *           <code>breakChars</code> is less than 32 or greater than 126; or
   *           if any character appears in <code>breakChars</code> more than
   *           once.
   */
  public CharacterAnalyzer(String breakChars) {
    isBreakChar = computeBreakChars(breakChars);
  }

  /**
   * Returns whether or not the specified character is a break character.
   * 
   * @param ch the character in question, must be in the range 0 to 255
   * @return <code>true</code> if <code>ch</code> is a break character;
   *         otherwise <code>false</code>
   * @throws ArrayIndexOutOfBoundsException if <code>ch</code> < 0 or
   *           <code>ch</code> > 255
   */
  public boolean isBreakChar(int ch) {
    return isBreakChar[ch];
  }

  /**
   * Stores whether each character from 0 to 255 is or is not a break character.
   */
  private final boolean[] isBreakChar;

}
